﻿using System;
using System.Collections.Generic;
using System.Linq;

using Microsoft.Xna.Framework;

namespace WorldOnFire
{
    public class CCamera : ABCClient
    {
        private Matrix m_mTransform;
        private Matrix m_mPerspective;

        public CCamera()
        {
            m_mTransform = Matrix.Identity;
            m_mPerspective = Matrix.Identity;
        }

        public void Initialize()
        {
            SEventSystem.RegisterClient(this, ABCEvent.eEventID.CAMERA_MOVE);
            SEventSystem.RegisterClient(this, ABCEvent.eEventID.CAMERA_ROTATE);
            SEventSystem.RegisterClient(this, ABCEvent.eEventID.CAMERA_SET_POSITION);
            SEventSystem.RegisterClient(this, ABCEvent.eEventID.CAMERA_LOOK_AT);
        }

        public override void HandleEvent(ABCEvent _Event)
        {
            // Handle input events
            switch ((int)_Event.eID)
            {
                case (int)ABCEvent.eEventID.CAMERA_LOOK_AT:
                    {
                        CEventCameraLookAt Event = (CEventCameraLookAt)_Event;

                        this.LookAt(Event.vLookAtPosition, Event.vCameraPosition);
                    }
                    break;

                case (int)ABCEvent.eEventID.CAMERA_SET_POSITION:
                    {
                        CEventCameraSetPosition Event = (CEventCameraSetPosition)_Event;

                        m_mTransform.Translation = Event.vPosition;
                    }
                    break;

                case (int)ABCEvent.eEventID.CAMERA_MOVE:
                    {
                        CEventCameraMove Event = (CEventCameraMove)_Event;

                        if (Event.bLocal == true)
                        {
                            this.TranslateLocal(Event.vVelocity);
                        }
                        else
                        {
                            this.TranslateWorld(Event.vVelocity);
                        }
                    }
                    break;

                case (int)ABCEvent.eEventID.CAMERA_ROTATE:
                    {
                        CEventCameraRotate Event = (CEventCameraRotate)_Event;

                        if (Event.bLocal == true)
                        {
                            switch ((int)Event.eAxis)
                            {
                                case (int)CEventCameraRotate.eAxisID.X:
                                    {
                                        this.RotateLocalX(Event.fRotationSpeed);
                                    }
                                    break;
                                case (int)CEventCameraRotate.eAxisID.Y:
                                    {
                                        this.RotateLocalY(Event.fRotationSpeed);
                                    }
                                    break;
                                case (int)CEventCameraRotate.eAxisID.Z:
                                    {
                                        this.RotateLocalZ(Event.fRotationSpeed);
                                    }
                                    break;
                            }
                        }
                        else
                        {
                            switch ((int)Event.eAxis)
                            {
                                case (int)CEventCameraRotate.eAxisID.X:
                                    {
                                        this.RotateWorldX(Event.fRotationSpeed);
                                    }
                                    break;
                                case (int)CEventCameraRotate.eAxisID.Y:
                                    {
                                        this.RotateWorldY(Event.fRotationSpeed);
                                    }
                                    break;
                                case (int)CEventCameraRotate.eAxisID.Z:
                                    {
                                        this.RotateWorldZ(Event.fRotationSpeed);
                                    }
                                    break;
                            }
                        }
                    }
                    break;
            }
        }

        public Matrix GetTransform()
        { return m_mTransform; }

        public Matrix GetView()
        {
            Matrix mView = m_mTransform;

            mView.Translation = Vector3.Zero;
            mView = Matrix.Transpose(mView);

            Vector3 vTranslation = m_mTransform.Translation;

            Vector3 vViewPosition = new Vector3(-Vector3.Dot(vTranslation, m_mTransform.Right),
                - Vector3.Dot(vTranslation, m_mTransform.Up),
                - Vector3.Dot(vTranslation, m_mTransform.Backward));

            mView.Translation = vViewPosition;

            return mView; 
        }

        public Matrix GetPerspective()
        { return m_mPerspective; }

        public void SetPerspective(float _fFieldOfView, float _fAspectRatio, float _fNearPlane, float _fFarPlane)
        {
            Matrix.CreatePerspectiveFieldOfView(_fFieldOfView, _fAspectRatio, _fNearPlane, _fFarPlane, out m_mPerspective);
        }

        public void SetTransform(Matrix _mTransform)
        {
            m_mTransform = _mTransform;
        }

        public void SetPosition(Vector3 _vPosition)
        {
            m_mTransform.Translation = _vPosition;
        }

        public void SetPosition(float _fX, float _fY, float _fZ)
        {
            m_mTransform.Translation = new Vector3(_fX, _fY, _fZ);
        }

        public void LookAt(Vector3 _vLookAtPosition)
        {
            // Calculate the vector between the position to look to and the camera's position
            Vector3 vBetween = _vLookAtPosition - m_mTransform.Translation;

            // The Z axis is the opposite of the normalized between vector
            m_mTransform.Backward = Vector3.Normalize(-vBetween);

            // Calculate the X axis by crossing the camera's Z axis and world's Y axis
            m_mTransform.Right = Vector3.Normalize(
                Vector3.Cross(new Vector3(0.0f, 1.0f, 0.0f), m_mTransform.Backward));

            // Calculate the Y axis by crossing the cameras other two axes
            m_mTransform.Up = Vector3.Normalize(
                Vector3.Cross(m_mTransform.Backward, m_mTransform.Right));
        }

        public void LookAt(Vector3 _vLookAtPosition, Vector3 _vCameraPosition)
        {
            // Set the camera's position
            m_mTransform.Translation = _vCameraPosition;

            // Run the look-at algorithm
            this.LookAt(_vLookAtPosition);
        }

        public void TranslateWorld(Vector3 _vTranslation)
        {
            m_mTransform.Translation += _vTranslation;
        }

        public void TranslateLocal(Vector3 _vTranslation)
        {
            // Project the translation along the local axes
            _vTranslation = _vTranslation.X * m_mTransform.Right +
                _vTranslation.Y * m_mTransform.Up +
                _vTranslation.Z * m_mTransform.Backward;

            m_mTransform.Translation += _vTranslation;
        }

        public void RotateWorldX(float _fRadians)
        {
            Vector3 vTranslation = m_mTransform.Translation;
            m_mTransform.Translation = Vector3.Zero;

            Matrix mRotation = Matrix.CreateRotationX(_fRadians);

            m_mTransform = m_mTransform * mRotation;
            m_mTransform.Translation = vTranslation;
        }

        public void RotateWorldY(float _fRadians)
        {
            Vector3 vTranslation = m_mTransform.Translation;
            m_mTransform.Translation = Vector3.Zero;

            Matrix mRotation = Matrix.CreateRotationY(_fRadians);

            m_mTransform = m_mTransform * mRotation;
            m_mTransform.Translation = vTranslation;
        }

        public void RotateWorldZ(float _fRadians)
        {
            Vector3 vTranslation = m_mTransform.Translation;
            m_mTransform.Translation = Vector3.Zero;

            Matrix mRotation = Matrix.CreateRotationZ(_fRadians);

            m_mTransform = m_mTransform * mRotation;
            m_mTransform.Translation = vTranslation;
        }

        public void RotateLocalX(float _fRadians)
        {
            Vector3 vTranslation = m_mTransform.Translation;
            m_mTransform.Translation = Vector3.Zero;

            Matrix mRotation = Matrix.CreateRotationX(_fRadians);

            m_mTransform = mRotation * m_mTransform;
            m_mTransform.Translation = vTranslation;
        }

        public void RotateLocalY(float _fRadians)
        {
            Vector3 vTranslation = m_mTransform.Translation;
            m_mTransform.Translation = Vector3.Zero;

            Matrix mRotation = Matrix.CreateRotationY(_fRadians);

            m_mTransform = mRotation * m_mTransform;
            m_mTransform.Translation = vTranslation;
        }

        public void RotateLocalZ(float _fRadians)
        {
            Vector3 vTranslation = m_mTransform.Translation;
            m_mTransform.Translation = Vector3.Zero;

            Matrix mRotation = Matrix.CreateRotationZ(_fRadians);

            m_mTransform = mRotation * m_mTransform;
            m_mTransform.Translation = vTranslation;
        }
    }
}
